﻿using System;
using System.Linq;
using Microsoft.Services.Clab.UI.Mvvmc.Properties;

namespace Microsoft.Services.Clab.UI.Mvvmc
{
    public class ControllerMetadata
    {
        private CodeControllerBase controller;

        internal ControllerMetadata(CodeControllerBase controller)
        {
            this.controller = controller;
        }

        public const string StartupActionName = "__StartupAction__";
        public const string UnloadViewActionName = "__UnloadViewAction__";

        internal static T ConvertParameter<T>(object[] parameters, int position)
        {
            try
            {
                object p = parameters.ElementAtOrDefault(position);

                if (p == null)
                {
                    return default(T);
                }
                else
                {
                    return (T)p;
                }
            }
            catch (InvalidCastException)
            {
                return default(T);
            }
        }

        //public void MapStartupActionToMethod(Action execute)
        //{
        //    this.MapActionToMethodImpl(StartupActionName, p => execute(), p => true);
        //}

        //public void MapUnloadViewActionToMethod(Action execute)
        //{
        //    this.MapActionToMethodImpl(UnloadViewActionName, p => execute(), p => true);
        //}

        //public void MapUnloadViewActionToMethod(Action<IView> execute)
        //{
        //    this.MapActionToMethodImpl(UnloadViewActionName, p => execute(ConvertParameter<IView>(p, 0)), p => true);
        //}

        //public void MapActionToMethod(string actionName, Action execute)
        //{
        //    this.MapActionToMethodImpl(actionName, p => execute(), p => true);
        //}

        //public void MapActionToMethod(string actionName, Action execute, Func<bool> canExecute)
        //{
        //    this.MapActionToMethodImpl(actionName, p => execute(), p => canExecute());
        //}

        //public void MapActionToMethod<T>(string actionName, Action<T> execute)
        //{
        //    this.MapActionToMethodImpl(actionName, p => execute(ConvertParameter<T>(p, 0)), p => true);
        //}

        //public void MapActionToMethod<T>(string actionName, Action<T> execute, Func<T, bool> canExecute)
        //{
        //    this.MapActionToMethodImpl(actionName, p => execute(ConvertParameter<T>(p, 0)), p => canExecute(ConvertParameter<T>(p, 0)));
        //}

        //public void MapActionToMethod<T0, T1>(string actionName, Action<T0, T1> execute)
        //{
        //    this.MapActionToMethodImpl(actionName, p => execute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1)), p => true);
        //}

        //public void MapActionToMethod<T0, T1>(string actionName, Action<T0, T1> execute, Func<T0, T1, bool> canExecute)
        //{
        //    this.MapActionToMethodImpl(actionName, p => execute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1)), p => canExecute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1)));
        //}

        //public void MapActionToMethod<T0, T1, T2>(string actionName, Action<T0, T1, T2> execute)
        //{
        //    this.MapActionToMethodImpl(actionName, p => execute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1), ConvertParameter<T2>(p, 2)), p => true);
        //}

        //public void MapActionToMethod<T0, T1, T2>(string actionName, Action<T0, T1, T2> execute, Func<T0, T1, T2, bool> canExecute)
        //{
        //    this.MapActionToMethodImpl(actionName, p => execute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1), ConvertParameter<T2>(p, 2)), p => canExecute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1), ConvertParameter<T2>(p, 2)));
        //}

        //private void MapActionToMethodImpl(string actionName, Action<object[]> doAction, Func<object[], bool> canDoAction)
        //{
        //    if (this.controller.MappedActions.Any(t => t.Item1 == actionName))
        //    {
        //        throw new InvalidOperationException(string.Format(Resources.ActionAlreadyRegistered, actionName));
        //    }
        //    this.controller.MappedActions.Add(Tuple.Create(actionName, doAction, canDoAction));
        //}

        public void MapActionToMethod(string actionName, ExecuteActionSignature execute, CanExecuteActionSignature canExecute = null)
        {
            if (canExecute != null)
            {
                this.MapActionToMethodImpl(actionName,
                    (object[] p) => execute(),
                    (ref bool r, object[] p) => canExecute(ref r));
            }
            else
            {
                this.MapActionToMethodImpl(actionName,
                    (object[] p) => execute(),
                    null);
            }
        }

        public void MapActionToMethod<T>(string actionName, ExecuteActionSignature<T> execute, CanExecuteActionSignature<T> canExecute = null)
        {
            if (canExecute != null)
            {
                this.MapActionToMethodImpl(actionName,
                     (object[] p) => execute(ConvertParameter<T>(p, 0)),
                     (ref bool r, object[] p) => canExecute(ref r, ConvertParameter<T>(p, 0)));
            }
            else
            {
                this.MapActionToMethodImpl(actionName,
                    (object[] p) => execute(ConvertParameter<T>(p, 0)),
                    null);
            }
        }

        public void MapActionToMethod<T0, T1>(string actionName, ExecuteActionSignature<T0, T1> execute, CanExecuteActionSignature<T0, T1> canExecute = null)
        {
            if (canExecute != null)
            {
                this.MapActionToMethodImpl(actionName,
                    (object[] p) => execute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1)),
                    (ref bool r, object[] p) => canExecute(ref r, ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1)));
            }
            else
            {
                this.MapActionToMethodImpl(actionName,
                    (object[] p) => execute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1)),
                    null);
            }
        }

        public void MapActionToMethod<T0, T1, T2>(string actionName, ExecuteActionSignature<T0, T1, T2> execute, CanExecuteActionSignature<T0, T1, T2> canExecute = null)
        {
            if (canExecute != null)
            {
                this.MapActionToMethodImpl(actionName,
                    (object[] p) => execute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1), ConvertParameter<T2>(p, 2)),
                    (ref bool r, object[] p) => canExecute(ref r, ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1), ConvertParameter<T2>(p, 2)));
            }
            else
            {
                this.MapActionToMethodImpl(actionName,
                    (object[] p) => execute(ConvertParameter<T0>(p, 0), ConvertParameter<T1>(p, 1), ConvertParameter<T2>(p, 2)),
                    null);
            }

        }

        private void MapActionToMethodImpl(string actionName, ExecuteActionSignature<object[]> doAction, CanExecuteActionSignature<object[]> canDoAction)
        {
            if (this.controller.MappedActions.Any(t => t.Item1 == actionName))
            {
                throw new InvalidOperationException(string.Format(Resources.ActionAlreadyRegistered, actionName));
            }
            this.controller.MappedActions.Add(Tuple.Create(actionName, doAction, canDoAction));
        }
    }

    public delegate void ExecuteActionSignature();
    public delegate void CanExecuteActionSignature(ref bool result);

    public delegate void ExecuteActionSignature<T>(T arg);
    public delegate void CanExecuteActionSignature<T>(ref bool result, T arg);

    public delegate void ExecuteActionSignature<T0, T1>(T0 arg0, T1 arg1);
    public delegate void CanExecuteActionSignature<T0, T1>(ref bool result, T0 arg0, T1 arg1);

    public delegate void ExecuteActionSignature<T0, T1, T2>(T0 arg0, T1 arg1, T2 arg2);
    public delegate void CanExecuteActionSignature<T0, T1, T2>(ref bool result, T0 arg0, T1 arg1, T2 arg2);
}
